// ASM: a very small and fast Java bytecode manipulation framework
// Copyright (c) 2000-2011 INRIA, France Telecom
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 3. Neither the name of the copyright holders nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
package xyz.noark.asm;

/**
 * A {@link ModuleVisitor} that generates the corresponding Module,
 * ModulePackages and ModuleMainClass attributes, as defined in the Java Virtual
 * Machine Specification (JVMS).
 *
 * @see <a href=
 *      "https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.25">JVMS
 *      4.7.25</a>
 * @see <a href=
 *      "https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.26">JVMS
 *      4.7.26</a>
 * @see <a href=
 *      "https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.27">JVMS
 *      4.7.27</a>
 * @author Remi Forax
 * @author Eric Bruneton
 */
final class ModuleWriter extends ModuleVisitor {

	/** Where the constants used in this AnnotationWriter must be stored. */
	private final SymbolTable symbolTable;

	/** The module_name_index field of the JVMS Module attribute. */
	private final int moduleNameIndex;

	/** The module_flags field of the JVMS Module attribute. */
	private final int moduleFlags;

	/** The module_version_index field of the JVMS Module attribute. */
	private final int moduleVersionIndex;

	/** The requires_count field of the JVMS Module attribute. */
	private int requiresCount;

	/**
	 * The binary content of the 'requires' array of the JVMS Module attribute.
	 */
	private final ByteVector requires;

	/** The exports_count field of the JVMS Module attribute. */
	private int exportsCount;

	/**
	 * The binary content of the 'exports' array of the JVMS Module attribute.
	 */
	private final ByteVector exports;

	/** The opens_count field of the JVMS Module attribute. */
	private int opensCount;

	/** The binary content of the 'opens' array of the JVMS Module attribute. */
	private final ByteVector opens;

	/** The uses_count field of the JVMS Module attribute. */
	private int usesCount;

	/**
	 * The binary content of the 'uses_index' array of the JVMS Module
	 * attribute.
	 */
	private final ByteVector usesIndex;

	/** The provides_count field of the JVMS Module attribute. */
	private int providesCount;

	/**
	 * The binary content of the 'provides' array of the JVMS Module attribute.
	 */
	private final ByteVector provides;

	/** The provides_count field of the JVMS ModulePackages attribute. */
	private int packageCount;

	/**
	 * The binary content of the 'package_index' array of the JVMS
	 * ModulePackages attribute.
	 */
	private final ByteVector packageIndex;

	/**
	 * The main_class_index field of the JVMS ModuleMainClass attribute, or 0.
	 */
	private int mainClassIndex;

	ModuleWriter(final SymbolTable symbolTable, final int name, final int access, final int version) {
		super(Opcodes.ASM7);
		this.symbolTable = symbolTable;
		this.moduleNameIndex = name;
		this.moduleFlags = access;
		this.moduleVersionIndex = version;
		this.requires = new ByteVector();
		this.exports = new ByteVector();
		this.opens = new ByteVector();
		this.usesIndex = new ByteVector();
		this.provides = new ByteVector();
		this.packageIndex = new ByteVector();
	}

	@Override
	public void visitMainClass(final String mainClass) {
		this.mainClassIndex = symbolTable.addConstantClass(mainClass).index;
	}

	@Override
	public void visitPackage(final String packaze) {
		packageIndex.putShort(symbolTable.addConstantPackage(packaze).index);
		packageCount++;
	}

	@Override
	public void visitRequire(final String module, final int access, final String version) {
		requires.putShort(symbolTable.addConstantModule(module).index).putShort(access).putShort(version == null ? 0 : symbolTable.addConstantUtf8(version));
		requiresCount++;
	}

	@Override
	public void visitExport(final String packaze, final int access, final String... modules) {
		exports.putShort(symbolTable.addConstantPackage(packaze).index).putShort(access);
		if (modules == null) {
			exports.putShort(0);
		} else {
			exports.putShort(modules.length);
			for (String module : modules) {
				exports.putShort(symbolTable.addConstantModule(module).index);
			}
		}
		exportsCount++;
	}

	@Override
	public void visitOpen(final String packaze, final int access, final String... modules) {
		opens.putShort(symbolTable.addConstantPackage(packaze).index).putShort(access);
		if (modules == null) {
			opens.putShort(0);
		} else {
			opens.putShort(modules.length);
			for (String module : modules) {
				opens.putShort(symbolTable.addConstantModule(module).index);
			}
		}
		opensCount++;
	}

	@Override
	public void visitUse(final String service) {
		usesIndex.putShort(symbolTable.addConstantClass(service).index);
		usesCount++;
	}

	@Override
	public void visitProvide(final String service, final String... providers) {
		provides.putShort(symbolTable.addConstantClass(service).index);
		provides.putShort(providers.length);
		for (String provider : providers) {
			provides.putShort(symbolTable.addConstantClass(provider).index);
		}
		providesCount++;
	}

	@Override
	public void visitEnd() {
		// Nothing to do.
	}

	/**
	 * Returns the number of Module, ModulePackages and ModuleMainClass
	 * attributes generated by this ModuleWriter.
	 *
	 * @return the number of Module, ModulePackages and ModuleMainClass
	 *         attributes (between 1 and 3).
	 */
	int getAttributeCount() {
		return 1 + (packageCount > 0 ? 1 : 0) + (mainClassIndex > 0 ? 1 : 0);
	}

	/**
	 * Returns the size of the Module, ModulePackages and ModuleMainClass
	 * attributes generated by this ModuleWriter. Also add the names of these
	 * attributes in the constant pool.
	 *
	 * @return the size in bytes of the Module, ModulePackages and
	 *         ModuleMainClass attributes.
	 */
	int computeAttributesSize() {
		symbolTable.addConstantUtf8(Constants.MODULE);
		// 6 attribute header bytes, 6 bytes for name, flags and version, and 5
		// * 2 bytes for counts.
		int size = 22 + requires.length + exports.length + opens.length + usesIndex.length + provides.length;
		if (packageCount > 0) {
			symbolTable.addConstantUtf8(Constants.MODULE_PACKAGES);
			// 6 attribute header bytes, and 2 bytes for package_count.
			size += 8 + packageIndex.length;
		}
		if (mainClassIndex > 0) {
			symbolTable.addConstantUtf8(Constants.MODULE_MAIN_CLASS);
			// 6 attribute header bytes, and 2 bytes for main_class_index.
			size += 8;
		}
		return size;
	}

	/**
	 * Puts the Module, ModulePackages and ModuleMainClass attributes generated
	 * by this ModuleWriter in the given ByteVector.
	 *
	 * @param output where the attributes must be put.
	 */
	void putAttributes(final ByteVector output) {
		// 6 bytes for name, flags and version, and 5 * 2 bytes for counts.
		int moduleAttributeLength = 16 + requires.length + exports.length + opens.length + usesIndex.length + provides.length;
		output.putShort(symbolTable.addConstantUtf8(Constants.MODULE)).putInt(moduleAttributeLength).putShort(moduleNameIndex).putShort(moduleFlags).putShort(moduleVersionIndex).putShort(requiresCount).putByteArray(requires.data, 0, requires.length)
				.putShort(exportsCount).putByteArray(exports.data, 0, exports.length).putShort(opensCount).putByteArray(opens.data, 0, opens.length).putShort(usesCount).putByteArray(usesIndex.data, 0, usesIndex.length).putShort(providesCount)
				.putByteArray(provides.data, 0, provides.length);
		if (packageCount > 0) {
			output.putShort(symbolTable.addConstantUtf8(Constants.MODULE_PACKAGES)).putInt(2 + packageIndex.length).putShort(packageCount).putByteArray(packageIndex.data, 0, packageIndex.length);
		}
		if (mainClassIndex > 0) {
			output.putShort(symbolTable.addConstantUtf8(Constants.MODULE_MAIN_CLASS)).putInt(2).putShort(mainClassIndex);
		}
	}
}
